# coding=utf-8 
#!/usr/bin/env python

## ----- external imports
import web
from web import form
import time
from sqlalchemy.orm import scoped_session, sessionmaker

# Owl imports
import validate
import model
import enum
import webutil
import config
from model import User, Group
from model import commit, rollback

from model import engine

web.config.debug = config.DEBUG
render_old = web.template.render('webpages/')
render = web.template.render('webpages/', base='layout')

urls = ('/welcome', 'welcome',
        '/login', 'login',
        '^/user/([\w][\w]*)/groups/edit$', 'user_groups_edit',
        '^/user/([\w][\w]*)/edit$', 'user_edit',
        '^/user/([\w][\w]*)/groups$', 'user_groups',
        '/user/new', 'user_new',
        '^/user/([\w][\w]*)', 'user_view'
        )

#===============================================================================
# Messages
#===============================================================================
## create a load hook to move flash message from the session to the context
web.session.flash = {}
#TODO: implement test for message display 
def flash_msg():
    '''Hook function to set the context flash message and then clear the
    message from the session. This so that messages survive redirection''' 
    # set the context flash message 
    web.ctx.flash = web.session.flash or {} 
    # clear the flash messages from the session 
    web.session.flash = {} 

def flash(**kargs): 
    '''Helper function to add new flash messages
    >>> flash(message='Created new record')
    '''
    web.session.flash.update(kargs)
    
##-----  
## Handle sqlalchemy
##-----                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         

def load_sqla_xxx(handler):
    web.ctx.orm = scoped_session(model.Session) 
    print 17, web.ctx.orm
    try:
        print 18, handler
        return handler()
        print 180
    except web.HTTPError:
        
        web.ctx.orm.commit()
        print 19
        raise
    except:      
        web.ctx.orm.rollback()
        print 20
        raise
    finally:
        print 21
        web.ctx.orm.commit()
        

def load_sqla_yyy(handler):
    web.ctx.orm = scoped_session(model.Session) 
    try:
        print 33, handler
        return handler()
        print 34
    except:      
        web.ctx.orm.rollback()
        print 35
 

app = web.application(urls, globals()) ## define app
app.add_processor(web.loadhook(flash_msg))## handler for flash message
# app.add_processor(load_sqla) ## handler for sqlalchmey
 

#===============================================================================
# Sessions
#===============================================================================
# Normal production sessions

if config.DEBUG == True:
    # sessions necessary when debug set to true
    if web.config.get('_session') is None:
        session = web.session.Session(app, web.session.DiskStore('sessions'), {'name':''})
        web.config._session = session
    else:
        session = web.config._session
else:
    session = web.session.Session(app, web.session.DiskStore('sessions'), 
                                                                        initializer={'authentic':0})

def f_user_edit(u, render=True):
    '''Returns form html or object if "render" is set to false'''
    f = form.Form(
        form.Textbox("name",
            form.notnull,
            form.Validator("This name failed validation. Please try another",
                                                                    lambda y: validate.name(y)[0]),
            value=u.name,
            class_='inputtext write'),
        form.Textbox("email",
            form.notnull,
            form.Validator("This is not a valid email", lambda y: validate.do(y, 'Email()')[0]),
            value=u.email,
            class_='inputtext write'),        
        form.Checkbox('active', value='on', checked=u.active),
        form.Textbox("id", form.notnull,
            readonly=True,
            value=u.id, class_='inputtext readonly')  
            )
    if render: return f.render()
    return f
    
def f_user_insert(u, render=True):
    '''Create form for adding a user. Form Returns form html
                    or object if "render" is set to false'''
    f = form.Form(
        form.Textbox("name",
            form.notnull,
            form.Validator("This name failed validation. Please try another",
                                                                    lambda y: validate.name(y)[0]),
            value=u.name,
            class_='inputtext write'),
        form.Textbox("email",
            form.notnull,
            form.Validator("This is not a valid email", lambda y: validate.do(y, 'Email()')[0]),
            value=u.email,
            class_='inputtext write'),
        form.Password("password",
            form.notnull,
            form.Validator('Password must be more than 5', lambda x:len(x) > 5),
            class_='inputtext write')  
            )
    if render: return f.render()
    return f

def f_user_view(u, render=True):
    '''Creates read-only version of user form'''
    if u.active:
        active = 'True'
        active_class = 'inputtext readonly'
    else:
        active = 'False'
        active_class = 'inputtext inactive'
    f = form.Form(
        form.Textbox("name",
            form.notnull,
            value=u.name,
            readonly=True,
            class_='inputtext readonly'),
        form.Textbox("email",
            form.notnull,
            value=u.email,
            readonly=True,
            class_='inputtext readonly'),
        form.Textbox('active',
            value=active,
            readonly=True,
            class_=active_class),
        form.Textbox("id", form.notnull, readonly=True, value=u.id,
            class_='inputtext readonly')  
            )
    if render: return f.render()
    return f

def f_login():
        f = form.Form(
        form.Textbox("email",
            form.notnull,
            form.Validator("This is not a valid email", lambda y: validate.do(y, 'Email()')[0]),
            class_='inputtext write'),
        form.Password("password", form.notnull,
            form.Validator('Password must be more than 5 characters', lambda x:len(x) > 5),          
            class_='inputtext write')  
            )
        return f


class user_new: 
    def GET(self): 
        u = User()
        p = webutil.Page(web.ctx)
        p.form = f_user_insert(u)
        p.verb, p.noun = 'new', 'user'
        p.editable = True
        p.cache = True
        p.update()        
        return render.t_default(p)

    def POST(self):
        u = User() 
        form = f_user_insert(u, False) 
        p = webutil.Page(web.ctx)
        p.verb, p.noun = 'new', 'user'
        p.editable = True        
        if not form.validates():
            #Client side error 
            p.message = "Please check the values you have entered"           
        else:
            u = User(form.d.name)
            u.email = form.d.email
            u.password_hash = u.hash(form.d.password)
            
            # u.save(web.ctx.orm) #worked
            # print '45 ------------------', p.session
            u.save(p.session) 
            ## web.ctx.orm.add(u)
            
            # form.valid, message = commit(web.ctx.orm)
            form.valid, message = commit(p.session)
            if form.valid:
                message+= form.d.name
                flash(message=message)
                raise web.seeother('/user/{0}'.format(form.d.name))
            else:
                #Server side error
                p.message = message
        p.update()  
        p.form = form.render()      
        return render.t_default(p)
     
class user_view: 
    def GET(self, user_name=None):
        p = webutil.Page(web.ctx) 
        if not validate.not_number(user_name):
            user_name = int(user_name)
        u = User(user_name).read(p.session)       
        if u:
            groups = u.groups(p.session)
            # build data set for table showing groups belonged to
            data = []
            for g in groups:
                data.append([g.id, g.name])            
            table = webutil.Table(data,['id', 'name'],'Groups this user belongs to','100%')
            p.table = table.render()
            p.form = f_user_view(u)
            p.verb = 'view'
            p.noun = u.name
            p.update()
        else:
            p.message = "User not found"
            p.name = "Page not found"
        return render.t_default(p)
    
class user_edit: 
    def GET(self, user_name=None):
        p = webutil.Page(web.ctx)
        p.verb = 'edit' 
        u = User(user_name).read(p.session)
        if not u:
            p.message = 'User {0} does not exist'.format(user_name)
            p.noun = "Not found"
        else:
            p.form = f_user_edit(u)
            p.noun = u.name            
            p.editable = True   
        p.update()
        return render.t_default(p)

    def POST(self, user_name=None):
        u = User(user_name)
        formdata = web.input() 
        form = f_user_edit(u, False)
        p = webutil.Page(web.ctx)
        p.editable = True        
        if not form.validates():
            #Client side error 
            p.message = "Please check the values you have entered"           
        else:
            u = User(form.d.id)
            u = u.read(p.session)
            u.email = form.d.email
            u.active = formdata.has_key('active')
            u.name = form.d.name           
            u.save(p.session)
            form.valid, message = commit(p.session)
            #form.valid = u.save()
            if form.valid:
                #Success!
                raise web.seeother('/user/{0}'.format(form.d.id))
            else:
                #Server side error
                p.message = message
                rollback(p.session)
        p.update()  
        p.form = form.render()      
        return render.t_default(p)
    
class user_groups:

    def GET(self, user_name=None):
        p = webutil.Page(web.ctx) 
        if not validate.not_number(user_name):
            user_name = int(user_name)
        u = User(user_name).read(p.session)       
        if u:
            groups = u.groups(p.session)
            # build data set for table showing groups belonged to
            data = []
            for g in groups:
                data.append([g.id, g.name])            
            table = webutil.Table(data,['id', 'name'],
                                  'Groups this user belongs to','100%', 'Edit')
            p.table = table.render()
            # p.form = f_user_view(u)
            p.form = False
            p.verb = 'view groups'
            #p.session
            p.noun = u.name
            p.update()
        else:
            p.message = "User not found"
            p.name = "Page not found"
        return render.t_default(p)
    
class user_groups_edit:

    def GET(self, user_name=None): 
        p = webutil.Page(web.ctx)
        p.editable_table = True 
        if not validate.not_number(user_name):
            user_name = int(user_name)
        u = User(user_name).read(p.session)       
        if u:
            groups = u.group_links(p.session)
            # build data set for table showing groups belonged to
            data = []
            authorities = enum.dict_category(enum.authorities, ['group'])
            authorities.reverse()
            hidden = ''
            for g in groups:
                if g[1]:
                    # If the user is linked to the Group 
                    authority_new = form.Dropdown('levchg{0}'.format(g[0].id), 
                                                                authorities, value = g[1]).render()
                    hidden = form.Hidden('levhid{0}'.format(g[0].id), value = g[1]).render()
                    checkbox_link = ''
                    checkbox_unlink = '''<input type="checkbox" name=grprem{0}
                                                                value="unlink" />'''.format(g[0].id)
                    open = '<strong>'
                    close = '</strong>'
                else:
                    # If the user is NOT linked to the Group
                    authority_new = form.Dropdown('levadd{0}'.format(g[0].id), authorities).render()
                    checkbox_link = '''<input type="checkbox" name=grpadd{0}
                                                                value="link" />'''.format(g[0].id)
                    checkbox_unlink = ''
                    open = close = ''                
                data.append([g[0].id, '{0}{1}{2}'.format(open, g[0].name, close),
                                            authority_new, hidden, checkbox_link, checkbox_unlink])            
            table = webutil.Table(data,['id', 'name','authority','','add',
                    'remove'],'Groups this user belongs to','100%', None, True)
            p.table = table.render()
            p.form = False
            p.verb = 'edit group membership'
            p.noun = u.name
            p.update()
        else:
            p.message = "User not found"
            p.name = "Page not found"
        return render.t_default(p)
    

    def POST(self, user_name=None):
        '''Handles editing of group memberships'''  
        p = webutil.Page(web.ctx)
        data = web.input() 
        u = User(user_name).read(p.session)
        message = ""
        for d in data.keys():
            action = d[0:6]
            group_id = d[6:]
            #Add a group
            if action == 'grpadd' and data[d]:
                group_level = data['levadd{0}'.format(group_id)]
                g = Group(int(group_id)).read(p.session)
                valid, content = u.link(g, authority = int(group_level)) 
                if valid:
                    message += '''Added user {0} to group id {1} with 
                    level "{2}"<br />'''.format(
                            user_name, group_id, enum.index2text(
                                            enum.authorities, int(group_level)))
            #Changing the security level of a group membership
            if action == 'levchg':
                if not data[d] == data['levhid{0}'.format(group_id)]:
                    g = Group(int(group_id)).read() # rationalise these?
                    valid, content = u.update_link(g, int(data[d]))
                    if valid:
                        message += '''Change security level for 
            group id {0} membership to {1}<br />'''.format(group_id, data[d])
                    else:
                        message += '''Failed changing security level for
            group id {0} memebership to {1}<br />'''.format(group_id, data[d])                    
            #Removing a groups
            if action == 'grprem':
                g = Group(int(group_id)).read()
                valid, content = u.unlink(g)
                if valid:
                    message += 'Removed user {0} from group id {1}<br />'.format(user_name, group_id)
        name = u.name #user is lost after commit
        commit() #rollback?
        flash(message=message)
        raise web.seeother('/user/{0}/groups/edit'.format(name))
             
class login: 
    def GET(self):
        f = webutil.Found(web.ctx)
        ret_form = f_login()
        ret_form.path = f.path
        ret_form.noun = ''
        ret_form.verb = 'login'
        ret_form.message = None
        ret_form.no_cache = False
#        ret_form.d.name = 'login'
        ret_form.title = 'login'
        ret_form.editable = False
        ret_form.login = True
        ret_form.table = False
        return render.t_form(ret_form)
    
    def POST(self):
        form = f_login() 
        form.message = ''
        f = webutil.Found(web.ctx)
        form.path = f.path
        form.verb = 'login'
        form.noun = ''
        form.name = 'Login User'
        form.title = 'Login User'
        form.no_cache = True
        if not form.validates(): 
            # return render.formSimple(form)
            form.editable = False
            form.login = True
            form.message = "Please check the values you have entered"
            return render.t_form(form)
        else:
            u = User()
            u.email = form.d.email
            print 101, u.email
            n = u.read_email()
            print 202, n
            if n.authenticates(form.d.password):
                print 333, session
                session.authentic = 1
                session.email = n.email
                session.name = n.name
                print 444, session
                # session.save()
                # raise web.seeother('/user/welcome/{0}'.format(n.name))
                raise web.seeother('/welcome')
                #return session.name
            else:
                form.message = 'Login failed'   
                form.login = True
                form.editable = False    
                return render.t_form(form)
class welcome: 
    def GET(self): 
        n = webutil.Found(web.ctx)
        n.name = 'rrrrr'
        n.message = 'Hello to :' + session.name
        return render.t_notfound(n) 

    

if __name__ == "__main__":
    # switched off for sessions?
    if config.DEBUG: web.internalerror = web.debugerror 
    app.run()
    
#    print session
#    session.school = 'collis'
#    print session
#    print dir(session.store)

#TEST: get User not found page
